package com.tools.commons.exceptions.handler;

import com.tools.commons.utils.JsonUtil;
import com.tools.commons.exceptions.GlobalException;
import com.tools.commons.response.R;
import com.tools.commons.response.ResponseCode;
import com.tools.commons.response.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


/**
 * @author wangminggen
 */
@ControllerAdvice
@ResponseBody
@Slf4j
@Controller
@ConditionalOnWebApplication
public class ExceptionHandlerAdvice {
    @Value("${spring.profiles.active:prod}")
    private String profile;

    private static final String DEV = "dev";

    ErrorAttributes errorAttributes;

    public ExceptionHandlerAdvice(ErrorAttributes errorAttributes) {
        this.errorAttributes = errorAttributes;
    }

    // @RequestMapping("/error")
    // public Result<?> error(HttpServletRequest request, HttpServletResponse response) {
    //     return result(response, ResponseCode.BAD_REQUEST, getErrorDetail(request));
    // }

    private String de(HttpServletRequest request) {
        if (DEV.equals(profile)) {
            WebRequest requestAttributes = new ServletWebRequest(request);
            Map<String, Object> errorAttributes =
                    this.errorAttributes.getErrorAttributes(requestAttributes,
                            ErrorAttributeOptions.defaults()
                    );
            Integer status = (Integer) errorAttributes.get("status");
            if (status != null && status == 404) {
                String path = (String) errorAttributes.get("path");
                return String.format("%s不存在", path);
            }
            String s = JsonUtil.obj2String(errorAttributes);
            log.info("[{}]", s);
            return s;
        }
        return "请求错误！";
    }

    /**
     * 处理抛出的自定义异常
     *
     * @param e exception
     * @return 包装过错误的json结果
     */
    @ExceptionHandler(GlobalException.class)
    @ResponseBody
    public Result<?> globalException(GlobalException e, HttpServletResponse response) {
        if (log.isDebugEnabled()) {
            log.debug("处理抛出的自定义异常");
        }
        return result(response, e.getResponse(), e.getArgs());
    }

    //处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常，详情继续往下看代码
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<?> BindExceptionHandler(BindException e, HttpServletResponse response) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
        return result(response, ResponseCode.BAD_REQUEST, message);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<?> methodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletResponse response) {
        List<ObjectError> objectErrors = ex.getBindingResult().getAllErrors();
        if (!CollectionUtils.isEmpty(objectErrors)) {
            StringBuilder msgBuilder = new StringBuilder();
            for (ObjectError objectError : objectErrors) {
                msgBuilder.append(objectError.getDefaultMessage()).append(",");
            }
            String errorMessage = msgBuilder.toString();
            if (errorMessage.length() > 1) {
                errorMessage = errorMessage.substring(0, errorMessage.length() - 1);
            }
            return result(response, ResponseCode.BAD_REQUEST, errorMessage);
        }
        return result(response, ResponseCode.BAD_REQUEST, ex.getMessage());
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<?> handleMethodArgumentTypeMismatchException(Exception e, HttpServletResponse response) {
        e.printStackTrace();
        log.error("服务器发生错误 {}", e.getLocalizedMessage());
        return result(response, ResponseCode.BAD_REQUEST);
    }

    /**
     * NoHandlerFoundException：首先根据请求Url查找有没有对应的控制器，若没有则会抛该异常，也就是大家非常熟悉的404异常；
     * HttpRequestMethodNotSupportedException：若匹配到了（匹配结果是一个列表，不同的是http方法不同，如：Get、Post等），则尝试将请求的http方法与列表的控制器做匹配，若没有对应http方法的控制器，则抛该异常；
     * HttpMediaTypeNotSupportedException：然后再对请求头与控制器支持的做比较，比如content-type请求头，若控制器的参数签名包含注解@RequestBody，但是请求的content-type请求头的值没有包含application/json，那么会抛该异常（当然，不止这种情况会抛这个异常）；
     * MissingPathVariableException：未检测到路径参数。比如url为：/licence/{licenceId}，参数签名包含@PathVariable("licenceId")，当请求的url为/licence，在没有明确定义url为/licence的情况下，会被判定为：缺少路径参数；
     * MissingServletRequestParameterException：缺少请求参数。比如定义了参数@RequestParam("licenceId") String licenceId，但发起请求时，未携带该参数，则会抛该异常；
     * TypeMismatchException: 参数类型匹配失败。比如：接收参数为Long型，但传入的值确是一个字符串，那么将会出现类型转换失败的情况，这时会抛该异常；
     * HttpMessageNotReadableException：与上面的HttpMediaTypeNotSupportedException举的例子完全相反，即请求头携带了"content-type: application/json;charset=UTF-8"，但接收参数却没有添加注解@RequestBody，或者请求体携带的 json 串反序列化成 pojo 的过程中失败了，也会抛该异常；
     * HttpMessageNotWritableException：返回的 pojo 在序列化成 json 过程失败了，那么抛该异常；
     * HttpMediaTypeNotAcceptableException：未知；
     * ServletRequestBindingException：未知；
     * ConversionNotSupportedException：未知；
     * MissingServletRequestPartException：未知；
     * AsyncRequestTimeoutException：未知；
     *
     * @param e Exception
     * @return json
     */
    @ExceptionHandler({
            NoHandlerFoundException.class,
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            // BindException.class,
            // MethodArgumentNotValidException.class,
            HttpMediaTypeNotAcceptableException.class,
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            MissingServletRequestPartException.class,
            AsyncRequestTimeoutException.class
    })
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result<?> handleServletException(Exception e, HttpServletResponse response) {
        if (e instanceof HttpRequestMethodNotSupportedException) {
            log.info("Request method  not supported====> {}", e.getLocalizedMessage());
            return result(response, ResponseCode.METHOD_NOT_ALLOWED);
        }
        String message = "错误请求";

        if (e instanceof MissingServletRequestParameterException) {
            MissingServletRequestParameterException e1 = (MissingServletRequestParameterException) e;
            message = String.format("请求字段缺失, 类型为 %s，名称为 %s", e1.getParameterType(), e1.getParameterName());
        }
        return result(response, ResponseCode.BAD_REQUEST, message);
    }


    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result<?> handleException(Exception e, HttpServletResponse response) {
        e.printStackTrace();
        if (DEV.equals(profile)) {
            return result(response, ResponseCode.DEV_EXCEPTION, "服务器错误");
        }
        log.error("服务器发生错误 {}", e.getLocalizedMessage());
        return result(response, ResponseCode.BAD_REQUEST, "请求失败");
    }

    /**
     * 将异常转换为 json 并完成国际化
     *
     * @param code 响应的枚举
     * @param args 参数列表
     * @return json对象
     */
    private Result<?> result(HttpServletResponse response, ResponseCode code, String... args) {
        response.setStatus(code.getCode());
        return R.fail(null, code, args);
    }
}
